package middleware

import (
	"crypto/sha256"
	"encoding/hex"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"net/http"
	"time"
)

// Idempotent 幂等性中间件
type Idempotent struct {
	redisClient *redis.Client
	expireTime  time.Duration
}

// NewIdempotent 创建一个新的幂等性中间件实例
func NewIdempotent(redisClient *redis.Client, expireTime time.Duration) *Idempotent {
	return &Idempotent{
		redisClient: redisClient,
		expireTime:  expireTime,
	}
}

// GenerateKey 根据请求信息生成唯一键值
func (i *Idempotent) GenerateKey(c *gin.Context) string {
	// 获取请求ID（从请求头中）
	requestID := c.GetHeader("X-Request-ID")
	if requestID == "" {
		// 如果没有请求ID，则根据URL、方法和Body生成
		rawBody, _ := c.Get("body")
		bodyStr, _ := rawBody.(string)

		hash := sha256.New()
		hash.Write([]byte(c.Request.URL.String() + c.Request.Method + bodyStr))
		requestID = hex.EncodeToString(hash.Sum(nil))
	}

	return "idempotent:" + requestID
}

// Handle 幂等性处理中间件
func (i *Idempotent) Handle() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 生成请求的唯一键
		key := i.GenerateKey(c)

		// 尝试获取锁，如果获取成功则继续处理，否则说明是重复请求
		lockKey := key + ":lock"
		set, err := i.redisClient.SetNX(c, lockKey, "1", i.expireTime).Result()
		if err != nil || !set {
			// 获取锁失败，说明可能是重复请求或者正在处理中
			// 等待一段时间后再次检查原始key是否存在
			time.Sleep(100 * time.Millisecond)
			exists, _ := i.redisClient.Exists(c, key).Result()
			if exists > 0 {
				c.JSON(http.StatusOK, gin.H{
					"code": 200,
					"msg":  "请求已处理",
					"data": nil,
				})
				c.Abort()
				return
			}
		}
		defer i.redisClient.Del(c, lockKey)

		// 检查该请求是否已经处理过
		exists, err := i.redisClient.Exists(c, key).Result()
		if err != nil {
			c.Next()
			return
		}

		// 如果已经存在，说明是重复请求，直接返回成功
		if exists > 0 {
			val, err := i.redisClient.Get(c, key).Result()
			if err == nil {
				c.JSON(http.StatusOK, gin.H{
					"code": 200,
					"msg":  "请求已处理",
					"data": val,
				})
				c.Abort()
				return
			}
		}
		// 记录请求开始处理
		i.redisClient.Set(c, key+":processing", time.Now().Unix(), i.expireTime)

		// 继续处理请求
		c.Next()

		// 请求处理完成后，将键值存入Redis，设置过期时间
		// 保存响应结果
		responseData := map[string]interface{}{
			"status": c.Writer.Status(),
			"data":   c.Value("response_data"), // 假设业务逻辑会设置响应数据
		}

		i.redisClient.Set(c, key, responseData, i.expireTime)
		i.redisClient.Del(c, key+":processing")
	}
}
